﻿package src.computation 
{
	/**
	* 
	* Reliability project
	* -----------------------
	* @author  Dmitrij Boycov
	* @mail    dmitry.boycov@gmail.com
	* @date    April 27, 2010
	* @version 1
	* 
	*/
	
	import flash.utils.Dictionary;	
	import flash.events.Event;
	import flash.text.TextFormat;	
	// src
	import src.computation.Method;
	import src.util.Utils;
	import src.entity.Node;
	import src.entity.Element;
	import src.util.Utils;
	import src.util.Container;
	import src.common.MsgEvent;
	// astra	
	import com.yahoo.astra.fl.charts.*;  
    import com.yahoo.astra.fl.charts.series.*;  
	import com.yahoo.astra.containers.formClasses.FormItem;
	import com.yahoo.astra.containers.formClasses.FormLayoutStyle;
	import com.yahoo.astra.layout.LayoutContainer;	
	import com.yahoo.astra.layout.modes.BoxLayout;
	
	public class Simulation extends Method{		
	
		protected var iterations:Number;
		protected var systemWorkTimes:Array = new Array();
		protected var iterationNumberInput;
		protected var chartX;
		protected var chartY;
		
		/**
		 * construct
		 */
		public function Simulation() { }
		
		
		/**
		 * 	unitSeriesElement
		 *  	+---+	    +---+         +---+ 
		 *     -|   |-------|   |-   =>  -|   |-
		 * 		+---+	    +---+         +---+
		 * 		 t1			  t2 	       min(t1;t2)
		 * @param	idBetweenLink
		 */
		protected override function unitSeriesElement(idBetweenLink) {
			var betweenLink:CLink = this.links[idBetweenLink];			
			if (betweenLink.end != null && betweenLink.start != null && 
				betweenLink.end["type"] == Element.TYPE && betweenLink.start["type"] == Element.TYPE) {
				var elOne:CElement = this.elements[betweenLink.start["id"]];
				var elTwo:CElement = this.elements[betweenLink.end["id"]];
				if (elOne != null && elTwo != null) {
					elOne.setTime(this.time);
					elTwo.setTime(this.time);
					var workTime = (elOne.getTimeOfWork() < elTwo.getTimeOfWork())?elOne.getTimeOfWork():elTwo.getTimeOfWork();
					elOne.setTimeOfWork(workTime);						
					var idNewLink;
					if (betweenLink.id == elTwo.inLink) {
						idNewLink = elTwo.outLink;
					}
					if (betweenLink.id == elTwo.outLink) {
						idNewLink = elTwo.inLink;
					}					
					elOne.unLink(betweenLink.id);
					elTwo.unLink(betweenLink.id);
					delete this.links[betweenLink.id];
					elTwo.unLink(idNewLink);
					elOne.addLink(this.links[idNewLink], this.links);
					this.links[idNewLink].removeEnd(elTwo);
					this.links[idNewLink].addEnd(elOne);
					delete this.elements[elTwo.id];
				}
			}
		}
		
		/**
		 * getParallEl
		 * 			  | 	
		 * 	  +-------0-------+	
		 *    |				  |
		 * 	+---+			   	
		 *  |	|			  ?	
		 * 	+---+			   	
		 * 	  |			      |	
		 * 	  +-------0-------+
		 * 			  |	
		 * 
		 * @param	aEl - CElement
		 * @return 	CElement
		 */
		protected function getParallEl(aEl:CElement):CElement {
			if (aEl == null) return null;
			if (!aEl.inLink || !aEl.outLink) return null;
			var lIn:CLink = this.links[aEl.inLink];
			var lOut:CLink = this.links[aEl.outLink];
			var oneNode:CNode = null;
			var twoNode:CNode = null;
			if (lIn.end["type"] == Node.TYPE) {
				oneNode = this.nodes[lIn.end["id"]];
			}
			if (lIn.start["type"] == Node.TYPE) {
				oneNode = this.nodes[lIn.start["id"]];
			}
			if (lOut.end["type"] == Node.TYPE) {
				twoNode = this.nodes[lOut.end["id"]];
			}
			if (lOut.start["type"] == Node.TYPE) {
				twoNode = this.nodes[lOut.start["id"]];
			}
			if (twoNode == null || oneNode == null) return null;
			var lIt;
			for each(lIt in oneNode.links) {
				var lIterator:CLink = this.links[lIt];
				if (lIn != lIterator && lOut != lIterator) {
					var elIterator:CElement = null;
					if (lIterator.start != null && lIterator.start["type"] == Element.TYPE) {
						elIterator = this.elements[lIterator.start["id"]];
					}
					if (lIterator.end != null && lIterator.end["type"] == Element.TYPE) {
						elIterator = this.elements[lIterator.end["id"]];
					}
					if (elIterator != null) {
						if (twoNode.links[elIterator.inLink] != null || twoNode.links[elIterator.outLink] != null) {
							return elIterator;
						}
					}
				}
			}
			return null;
		}
		
		protected function reduceParallElements() {			
			var el:CElement;
			for each(el in this.elements) {
				var el2:CElement = this.getParallEl(el);
				if (el2 != null && el2 != el ) {
					// unit
					el2.setTime(this.time);
					el.setTime(this.time);
					var workTime = (el2.getTimeOfWork() > el.getTimeOfWork())?el2.getTimeOfWork():el.getTimeOfWork();					
					el.setTimeOfWork(workTime);
					var inL:CLink = this.links[el2.inLink];
					var outL:CLink = this.links[el2.outLink];
					var n1 = inL.getOtherEnd(el2);
					if(n1["type"]==Node.TYPE){
						inL.removeEnd(this.nodes[n1["id"]]);
					}	
					var n2 = outL.getOtherEnd(el2);
					if (n2["type"] == Node.TYPE) {
						outL.removeEnd(this.nodes[n2["id"]]);
					}					
					inL.removeEnd(el2);
					outL.removeEnd(el2);
					delete this.links[inL.id];
					delete this.links[outL.id];
					delete this.elements[el2.id];
					//
					this.modified = true;
				}
			}		
		}
		
		
		/**
		 *  transformTriangle
		 *			|						   |
		 * 	  +-----0-----+				  +----0----+	
		 * 	  |		u1	  |				  |	 u1	    |
		 * 	+---+		+---+			+---+     +---+  
		 * 	|e1 |		|e3 |     =>  	|e1'|	  |e3'|
		 * 	+---+		+---+			+---+	  +---+	
		 * 	  |	  +---+	  |				  |    		|
		 * u2 0---|e2 |---0 u3			  0 u2  	0 u3
		 * 	  |	  +---+	  |				  |         |	
		 * 
		 * @param	e1
		 * @param	e2
		 * @param	e3
		 * @param	u1
		 * @param	u2
		 * @param	u3
		 */
		protected override function transformTriangle(e1:CElement, e2:CElement, e3:CElement, u1:CNode, u2:CNode, u3:CNode) {
			if (e1 == null || e2 == null || e3 == null || u1 == null || u2 == null || u3 == null) return;			
			e1.setTime(this.time);
			e2.setTime(this.time);
			e3.setTime(this.time);
			var t1 = e1.getTimeOfWork();
			var t2 = e2.getTimeOfWork();
			var t3 = e3.getTimeOfWork();		
			e1.setTimeOfWork(Math.max(t1, Math.min(t3, t2)));
			e3.setTimeOfWork(Math.max(t3, Math.min(t1, t2)));			
			u2.unLink(e2.inLink); u2.unLink(e2.outLink); 
			u3.unLink(e2.inLink); u3.unLink(e2.outLink);
			delete this.links[e2.outLink];
			delete this.links[e2.inLink];
			delete this.elements[e2.id];
			this.modified = true;
		}		
		
		/**
		 * This method are need for save first version dictionary.
		 * Because each iterations change dict.
		 * @param	dict - Dictionary
		 * @return
		 */
		protected function cloneDict(dict):Dictionary {
			var buf = new Dictionary();
			if(dict !=null){
				for (var key in dict) {
					buf[key] = dict[key].clone();
				}
			}
			return buf;
		}
		
		public override function startComputation() {
			this.iterations = 1;
			var iter = Number(this.iterationNumberInput.text);
			iter = Math.round(iter);			
			if (iter != null && iter > 0) {
				this.iterations = iter;
			}
			if (this.nodes == null || this.elements == null || this.links == null) {				
				dispatchEvent(new MsgEvent(Method.ERROR, "Есть разрыв в схеме."));
				return false;
			}
			this.deleteFreeElements();			 
			var inNode = this.nodes[1];		
			if (Utils.sizeDict(inNode.links) > 0) {
				this.removeUnnecessaryNodes();				
				this.initElements();
				var bufElements = this.cloneDict(this.elements);
				var bufNodes = this.cloneDict(this.nodes);
				var bufLinks = this.cloneDict(this.links);				
				// loop by iteration
				for (var i = 0; i < this.iterations;i++){
					this.modified = true;
					// The base loop transform scheme to one element.					
					while (this.modified) {
						this.modified = false;
						this.reduceTriangle();
						this.reduceSeriesElemnts();
						this.reduceParallElements();
						this.removeUnnecessaryNodes();
					}
					
					if (Utils.sizeDict(this.elements) == 1) {
						var el:CElement;
						for each(el in this.elements) {	
							el.setTime(this.time);					
							this.systemWorkTimes.push(el);
						}
					}else {
						dispatchEvent(new MsgEvent(Method.ERROR, "Невозможно сократить схему."));
						return; 
					}			
					this.elements = this.cloneDict(bufElements);
					this.nodes = this.cloneDict(bufNodes);
					this.links = this.cloneDict(bufLinks);	
				}			
				dispatchEvent(new Event(Method.STOP_COMPUTATION));	
			}else { // inNode do not have link.				
				dispatchEvent(new MsgEvent(Method.ERROR,"Есть разрыв в схеме."));
			}	
		}
		
		public override function getResult() {			
			if (this.systemWorkTimes.length != 0 ) {				
				var sumTime = 0;
				var minTime = 0;
				var maxTime = 0;
				var n0 = 0;
				for (var i = 0; i < this.systemWorkTimes.length; i++ ) {
		//			trace("ti:" + ti);
					if(this.systemWorkTimes[i]!=null){
						var ti = this.systemWorkTimes[i].getTimeOfWork()
						if (ti > maxTime) { maxTime = ti; }
						if ( minTime > ti || minTime == 0) { minTime = ti; }
						if (ti < this.time) { n0++; }
						sumTime += ti;
					}
				}
				if (this.iterations == 0) {
					this.iterations = 1;					
				}
				// for chart				
				var num = 10;
				if (this.iterations < 10) {
					num = this.iterations;
				}
				var delta = (maxTime - minTime) / num;
				this.chartX = new Array();
				this.chartY = new Array();
				if (delta == 0) {
					this.chartX[0] = minTime;
					this.chartY[0] = this.systemWorkTimes.length;
				}else{
					for (var i = 0; i < num; i++ ) {
						this.chartX[i] = Math.round((minTime + delta * (i + 1))*100)/100;
						this.chartY[i] = 0;					
					}
					for (var i = 0; i < this.systemWorkTimes.length; i++ ) {				
						if(this.systemWorkTimes[i]!=null){
							var ti = this.systemWorkTimes[i].getTimeOfWork();
							var index = Math.floor((ti - minTime) / delta);
							this.chartY[index]++;
						}
					}
				}
				//
				var vbr = (this.iterations - n0) / this.iterations;
				var mediumTime = sumTime / this.iterations;
				var res = "Среднее время работы:" + mediumTime; 
				res += "\n ------------------------ \n";
				res += "Максимальное время работы:" + maxTime;
				res += "\n ------------------------ \n";
				res += "Минимальное время работы:" + minTime;
				res += "\n ------------------------ \n";
				res += "Вероятность безотказной работы:"+vbr;
				return res;
			}
			return null; 
		}
		
		public override function getResForm() {
			var box : BoxLayout = new BoxLayout();
			box.direction = "horizontal";
			box.horizontalGap = 5;			
			var cont = new LayoutContainer(box);	
			var chart:ColumnChart = new ColumnChart();
		//	chart.verticalField  = "Время работы";
		//	chart.horizontalAxisTitle = "Время работы";
		//	chart.verticalAxisTitle  = "Кол-во";
			chart.setStyle("horizontalAxisLabelRotation", -45);
			chart.setStyle("horizontalAxisTitleTextFormat", new TextFormat("Arial", 12, 0x000000, true));  
			chart.x = 10;  
			chart.y = 10;  
			chart.width = 350;  
			chart.height = 250;  
			cont.addChild( chart );
			chart.categoryNames = this.chartX;  
			chart.dataProvider = this.chartY; 
			cont.x = 0;
			return cont;
		}
		
		
		public override function getParamForm() {
			this.iterationNumberInput = Utils.numberInputText();
			var iterFormItem = new FormItem("Количество \nповторений", this.iterationNumberInput);
			iterFormItem.labelAlign = FormLayoutStyle.LEFT;
			iterFormItem.labelWidth = 100;
			var box : BoxLayout = new BoxLayout();
			box.direction = "vertical";
			box.horizontalGap = 10;			
			var container = new LayoutContainer(box);
			container.autoMask = false;
			container.addChild(iterFormItem);
			return container;
		}
		
	}
	
}